#include "Screen.h"

#ifdef __cplusplus
extern "C"
{
#endif
void if_threads_send();
void if_call_stack_send();
#ifdef __cplusplus
}
#endif

const char* Screen::m_readline_history_filename = HISTORY_FILE_NAME;
char* Screen::m_readline_history_path = NULL;
Screen Screen::m_instance;

/**
 * Runs a command in the shell.  The shell may be interactive, and CGDB
 * will be paused for the duration of the shell.  Any leading stuff, like
 * 'shell ' or '!' should be removed prior to calling this function.
 *
 * \param command The command to run at the shell.  Empty string or null
 *                means to invoke an interactive shell.
 *
 * \return The exit status of the system() call.
 */
/*CallBack*/int run_shell_command(const char *command) {
    Screen* screen = Screen::GetInstance(); 
    return screen->Run_shell_command(command);
}
/*CallBack*/int tab_completion (int a, int b)
{
    Screen* screen = Screen::GetInstance(); 
    return screen->Tab_completion(a, b);
}

/**
 * This is the function responsible for display the readline completions when there
 * is more than 1 item to complete. Currently it prints 1 per line.
 */
/*CallBack*/void readline_completion_display_func (char **matches, int num_matches, int max_length)
{
    Screen* screen = Screen::GetInstance(); 
    screen->Ridline_completion_display_func (matches, num_matches, max_length);
}

/*CallBack*/int update_kui(cgdbrc_config_option_ptr option)
{
    Screen* screen = Screen::GetInstance(); 
    return screen->Update_kui(option);
}

/*CallBack*/int destroyReadlineKeySeq (void *data)
{
    char *keyseq = (char*)data;
    free (keyseq);
    return 0;
}


/* readline code {{{*/

/* Please forgive me for adding all the comment below. This function
 * has some strange bahaviors that I thought should be well explained.
 */
/*CallBack*/void rlctx_send_user_command (char *line)
{
    Screen* screen = Screen::GetInstance(); 
    screen->Send_user_command(line);
}


int Screen::Initialize(Ctgdb* tgdb, char* debugger_path, int argc, char** argv)
{
    int result = 0;
    tgdb_request_ptr request_ptr;
    if(tgdb == NULL)
        return -1;

    m_tgdb = tgdb;
    m_current_line = ibuf_init();

    cgdbrc_init();

    if (Create_and_init_pair () == -1)
    {
        fprintf (stderr, "%s:%d Unable to create PTY pair", __FILE__, __LINE__);
        return -1;
    }

    result = m_tgdb->Initialize(debugger_path, argc, argv, &m_gdb_fd, &m_tty_fd);
    if(result != 0)
        return result;
    
    /* Run some initialize commands */

    /* gdb may already have some breakpoints when it starts. This could happen
     * if the user puts breakpoints in there .gdbinit.
     * This makes sure that TGDB asks for the breakpoints on start up.
     */

    /**
     * Get the filename GDB defaults to. 
     */
    request_ptr = m_tgdb->Request_current_location(1);
    if (Handle_request(request_ptr) == -1)
        return -1;

    /* From here on, the logger is initialized */

    /* Create the home directory */
    if (Init_home_dir() == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "Unable to create home dir ~/.cgdb");
        Cleanup();
        return -1;
    }

    if (Init_readline() == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "Unable to init readline");
        Cleanup();
        return -1;
    }

    if (tty_cbreak(STDIN_FILENO, &m_term_attributes) == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__, "tty_cbreak error");
        Cleanup();
        return -1;
    }

    if (Init_kui() == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__, "init_kui error");
        Cleanup();
        return -1;
    }

    /* Initialize the display */
    switch (if_init ())
    {
        case 1:
            logger_write_pos (logger, __FILE__, __LINE__,
                    "Unable to initialize the curses library");
            Cleanup();
            return -1;
        case 2:
            logger_write_pos (logger, __FILE__, __LINE__,
                    "Unable to handle signal: SIGWINCH");
            Cleanup();
            return -1;
        case 3:
            logger_write_pos (logger, __FILE__, __LINE__,
                    "Unable to setup highlighting groups");
            Cleanup();
            return -1;
        case 4:
            logger_write_pos (logger, __FILE__, __LINE__,
                    "New GDB window failed -- out of memory?");
            Cleanup();
            return -1;
    }

    /* Initialize the pipe that is used for resize */
    if (Init_resize_pipe () == -1)
    {
        logger_write_pos (logger, __FILE__, __LINE__, "Init_resize_pipe error");
        Cleanup();
        return -1;
    }

    {
        char config_file[FSUTIL_PATH_MAX];
        FILE *config;
        fs_util_get_path (m_cgdb_home_dir, "cgdbrc", config_file);
        config = fopen (config_file, "r");
        if (config)
        {
            command_parse_file (config);
            fclose (config);
        }
    }

    return 0;

}

/**
 * If the TGDB instance is not busy, it will run the requested command.
 * Otherwise, the command will get queued to run later.
 *
 * \param tgdb
 * An instance of the tgdb library to operate on.
 *
 * \param request
 * The requested command to have TGDB process.
 *
 * \return
 * 0 on success or -1 on error
 */
int Screen::Handle_request(struct tgdb_request *request)
{
    int val, is_busy;

    if (!request)
        return -1;

    val = m_tgdb->IsBusy(&is_busy);
    if (val == -1)
        return -1;

    if (is_busy)
        m_tgdb->Queue_append(request);
    else
    {
        m_last_request = request;
        m_tgdb->ProcessCommand(request);
    }

    return 0;
}

int Screen::Update_kui(cgdbrc_config_option_ptr option)
{
    kui_manager_set_terminal_escape_sequence_timeout (m_kui_ctx, cgdbrc_get_key_code_timeoutlen ());
    kui_manager_set_key_mapping_timeout (m_kui_ctx, cgdbrc_get_mapped_key_timeoutlen ());

    return 0;
}

int Screen::MainLoop()
{
    fd_set rset;
    int max;
    int masterfd, slavefd;

    masterfd = pty_pair_get_masterfd(m_pty_pair);
    if (masterfd == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "pty_pair_get_masterfd error");
        return -1;
    }

    slavefd = pty_pair_get_slavefd (m_pty_pair);
    if (slavefd == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "pty_pair_get_slavefd error");
        return -1;
    }


    max = (m_gdb_fd > STDIN_FILENO) ? m_gdb_fd : STDIN_FILENO;
    max = (max > m_tty_fd) ? max : m_tty_fd;
    max = (max > m_resize_pipe[0]) ? max : m_resize_pipe[0];
    max = (max > slavefd) ? max : slavefd;
    max = (max > masterfd) ? max : masterfd;

    /* Main (infinite) loop:
     *   Sits and waits for input on either stdin (user input) or the
     *   GDB file descriptor.  When input is received, wrapper functions
     *   are called to process the input, and handle it appropriately.
     *   This will result in calls to the curses interface, typically. */

    for (;;)
    {
        /* Reset the fd_set, and watch for input from GDB or stdin */
        FD_ZERO (&rset);
        FD_SET (STDIN_FILENO, &rset);
        FD_SET (m_gdb_fd, &rset);
        FD_SET (m_tty_fd, &rset);
        FD_SET (m_resize_pipe[0], &rset);

        /* No readline activity allowed while displaying tab completion */
        if (!m_is_tab_completing)
        {
            FD_SET (slavefd, &rset);
            FD_SET (masterfd, &rset);
        }

        /* Wait for input */
        if (select (max + 1, &rset, NULL, NULL, NULL) == -1)
        {
            if (errno == EINTR)
                continue;
            else
            {
                m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                        "select failed: %s", strerror (errno));
                return -1;
            }
        }

        /* Input received through the pty:  Handle it 
         * Wrote to masterfd, now slavefd is ready, tell readline */
        if (FD_ISSET (slavefd, &rset))
            rline_rl_callback_read_char (m_rline);

        /* Input received through the pty:  Handle it
         * Readline read from slavefd, and it wrote to the masterfd. 
         */
        if (FD_ISSET (masterfd, &rset))
            if (Readline_input () == -1)
                return -1;

        /* Input received:  Handle it */
        if (FD_ISSET (STDIN_FILENO, &rset)) {
            int val = User_input_loop ();
            /* The below condition happens on cygwin when user types ctrl-z
             * select returns (when it shouldn't) with the value of 1. the
             * user input loop gets called, the kui gets called and does a
             * non blocking read which returns EAGAIN. The kui then passes
             * the -1 up the stack with out making any more system calls. */
            if (val == -1 && errno == EAGAIN)
                continue;
            else if (val == -1)
                return -1;
        }

        /* child's ouptut -> stdout
         * The continue is important I think. It allows all of the child
         * output to get written to stdout before tgdb's next command.
         * This is because sometimes they are both ready.
         */
        if (FD_ISSET (m_tty_fd, &rset))
        {
            if (Child_input() == -1)
                return -1;
            continue;
        }

        /* gdb's output -> stdout */
        if (FD_ISSET (m_gdb_fd, &rset)) {
            if (Gdb_input () == -1) {
                return -1;
            }

            /* When the file dialog is opened, the user input is blocked, 
             * until GDB returns all the files that should be displayed,
             * and the file dialog can open, and be prepared to receive 
             * input. So, if we are in the file dialog, and are no longer
             * waiting for the gdb command, then read the input.
             */
            if (kui_manager_cangetkey (m_kui_ctx)) {
                User_input_loop();
            }
        }

        /* A resize signal occured */
        if (FD_ISSET (m_resize_pipe[0], &rset))
            if (Resize_term (m_resize_pipe[0]) == -1)
                return -1;
    }
    return 0;
}

/**
 * This will usually process all the input that the KUI has.
 *
 * However, it's possible that one of the keys the user sends to CGDB 
 * switches the state of CGDB in such a way, that CGDB has to do some I/O
 * with GDB before the keys can continue to be sent to CGDB. For this reason,
 * this loop can return before all the KUI's data has been used, in order to
 * give the main loop a chance to run a GDB command.
 *
 * \return
 * 0 on success or -1 on error
 */
int Screen::User_input_loop()
{
    do {
        /* There are reasons that CGDB should wait to get more info from the kui.
         * See the documentation for m_kui_input_acceptable */
        if (!m_kui_input_acceptable)
            return 0;

        if (User_input () == -1) {
            m_tgdb->Logger_write_pos( __FILE__, __LINE__, "User_input_loop failed");
            return -1;
        }
    } while (kui_manager_cangetkey (m_kui_ctx));

    return 0;
}


void Screen::Send_user_command(char *line)
{
    char *cline;
    int length;
    char *rline_prompt;
    tgdb_request_ptr request_ptr;

    if (line == NULL) {
        /* NULL line means rl_callback_read_char received EOF */
        ibuf_add(m_current_line, "quit");
    } else {
        /* Add the line passed in to the current line */
        ibuf_add (m_current_line, line);
    }

    /* Get current line, and current line length */
    cline = ibuf_get (m_current_line);
    length = ibuf_length (m_current_line);

    /* Check to see if the user is escaping the line, to use a 
     * multi line command. If so, return so that the user can
     * continue the command. This data should not go into the history
     * buffer, or be sent to gdb yet. 
     *
     * Also, notice the length > 0 condition. (length == 0) can happen 
     * when the user simply hits Enter on the keyboard. */
    if (length > 0 && cline[length - 1] == '\\')
    {
        /* The \ char is for continuation only, it is not meant to be sent
         * to GDB as a character. */
        ibuf_delchar (m_current_line);

        /* Only need to change the prompt the first time the \ char is used.
         * Doing it a second time would erase the real m_rline_last_prompt,
         * since the prompt has already been set to "".
         */
        if (!m_rline_last_prompt)
        {
            rline_get_prompt (m_rline, &rline_prompt);
            m_rline_last_prompt = strdup (rline_prompt);
            /* GDB set's the prompt to nothing when doing continuation.
             * This is here just for compatibility. */
            rline_set_prompt (m_rline, "");
        }

        return;
    }

    /* If here, a full command has been sent. Restore the prompt. */
    if (m_rline_last_prompt)
    {
        rline_set_prompt (m_rline, m_rline_last_prompt);
        free (m_rline_last_prompt);
        m_rline_last_prompt = NULL;
    }

    /* Don't add the enter command to the history */
    if (length > 0)
        rline_add_history (m_rline, cline);


    if (length > 0 || get_last_cmd() == NULL)
        request_ptr = m_tgdb->Request_run_console_command(cline);
    else
        request_ptr = m_tgdb->Request_run_console_command(get_last_cmd()); 
    if (!request_ptr)
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "rlctx_send_user_command\n");

    /* Send this command to TGDB */
    Handle_request(request_ptr);

    ibuf_clear (m_current_line);
}

int Screen::Init_readline (void)
{
    int slavefd, masterfd;
    int length;
    slavefd = pty_pair_get_slavefd (m_pty_pair);
    if (slavefd == -1)
        return -1;

    masterfd = pty_pair_get_masterfd (m_pty_pair);
    if (masterfd == -1)
        return -1;

    if (tty_off_xon_xoff (slavefd) == -1)
        return -1;

    /* The 16 is because I don't know how many char's the directory separator 
     * is going to be, I expect it to be 1, but who knows. */
    length = strlen (m_cgdb_home_dir) + strlen (m_readline_history_filename) + 16;
    m_readline_history_path = (char *) malloc (sizeof (char) * length);
    fs_util_get_path (m_cgdb_home_dir, m_readline_history_filename,
            m_readline_history_path);
    m_rline = rline_initialize (slavefd, rlctx_send_user_command, tab_completion, "dumb");
    rline_read_history (m_rline, m_readline_history_path);
    return 0;
}

int Screen::Readline_input ()
{
    const int MAX = 1024;
    char *buf = (char*) malloc (MAX + 1);
    int size;

    int masterfd = pty_pair_get_masterfd (m_pty_pair);
    if (masterfd == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "pty_pair_get_masterfd error");
        return -1;
    }

    size = read (masterfd, buf, MAX);
    if (size == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__, "read error");
        free (buf);
        buf = NULL;
        return -1;
    }

    buf[size] = 0;

    /* Display GDB output 
     * The strlen check is here so that if_print does not get called
     * when displaying the filedlg. If it does get called, then the 
     * gdb window gets displayed when the filedlg is up
     */
    if (size > 0)
        if_print (buf);

    free (buf);
    buf = NULL;
    return 0;
}


/* Child_input: Recieves data from the child application:
 *
 *  Returns:  -1 on error, 0 on success
 */
int Screen::Child_input ()
{
    char *buf = (char*) malloc (GDB_MAXBUF + 1);
    int size;

    /* Read from GDB */
    size = m_tgdb->Recv_inferior_data(buf, GDB_MAXBUF);
    if (size == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "tgdb_recv_inferior_data error ");
        free (buf);
        buf = NULL;
        return -1;
    }
    buf[size] = 0;

    /* Display CHILD output */
    if_tty_print (buf);
    free (buf);
    buf = NULL;
    return 0;
}

int Screen::Init_resize_pipe(void)
{
    if (pipe (m_resize_pipe) == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__, "pipe error");
        return -1;
    }

    return 0;
}

int Screen::Resize_term(int fd)
{
    int c, result;
    read (m_resize_pipe[0], &c, sizeof (int));

    /* If there is more input in the pipe, that means another resize has
     * been recieved, and we still have not handled this one. So, skip this
     * one and only handle the next one.
     */
    result = io_data_ready (m_resize_pipe[0], 0);
    if (result == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__, "io_data_ready");
        return -1;
    }

    if (result)
        return 0;

    if (if_resize_term () == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__, "if_resize_term error");
        return -1;
    }

    return 0;
}


/* User_input: This function will get a key from the user and process it.
 *
 *  Returns:  -1 on error, 0 on success
 */
int Screen::User_input (void)
{
    static int key, val;

    /* Clear the current map sets. Give the KUI the map sets 
     * that should be used with the current focus.
     */
    val = kui_manager_clear_map_sets (m_kui_ctx);
    if (val == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__, "User_input error");
        return -1;
    }

    if (if_get_focus () == CGDB)
        val = kui_manager_add_map_set (m_kui_ctx, m_kui_map);
    else if (if_get_focus () == GDB)
        val = kui_manager_add_map_set (m_kui_ctx, m_kui_imap);

    key = kui_manager_getkey (m_kui_ctx);
    if (key == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "kui_manager_getkey error");
        return -1;
    }

    val = if_input (key);

    if (val == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__, "if_input error");
        return -1;
    }
    else if (val != 1 && val != 2)
        return 0;

    if (val == 1 && m_completion_ptr)
        return Handle_tab_completion_request (m_completion_ptr, key);

    /* Process the key */
    if (kui_term_is_cgdb_key (key))
    {
        char *seqbuf = kui_term_get_ascii_char_sequence_from_key (key);
        if (seqbuf == NULL)
        {
            m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                    "kui_term_get_ascii_char_sequence_from_key error %d",
                    key);
            return -1;
        }
        else
        {
            int length = strlen (seqbuf), i;
            for (i = 0; i < length; i++)
                Send_key (val, seqbuf[i]);
        }
    }
    else
        Send_key (val, key);

    return 0;
}


void Screen::Send_key (int focus, char key)
{
    if (focus == 1)
    {
        int size;
        int masterfd;
        masterfd = pty_pair_get_masterfd (m_pty_pair);
        if (masterfd == -1)
            m_tgdb->Logger_write_pos( __FILE__, __LINE__, "Send_key error");
        size = write (masterfd, &key, sizeof (char));
        if (size != 1)
            m_tgdb->Logger_write_pos( __FILE__, __LINE__, "Send_key error");
    }
    else if (focus == 2)
    {
        m_tgdb->Send_inferior_char(key);
    }
}

/**
 * The goal of this function is to display the tab completion information
 * to the user in an asychronous and potentially interactive manner.
 *
 * It will output to the screen as much as can be until user input is needed. 
 * If user input is needed, then this function must stop, and wait until 
 * that data has been recieved.
 *
 * If this function is called a second time with the same m_completion_ptr
 * parameter, it will continue outputting the tab completion data from
 * where it left off.
 *
 * \param m_completion_ptr
 * The tab completion data to output to the user
 *
 * \param key
 * The key the user wants to pass to the query command that was made.
 * If -1, then this is assummed to be the first time this function has been
 * called.
 *
 * \return
 * 0 on success or -1 on error
 */
int Screen::Handle_tab_completion_request (tab_completion_ptr comptr, int key)
{
    int query_items;
    int gdb_window_size;

    if (!comptr)
        return -1;

    query_items = rline_get_rl_completion_query_items (m_rline);
    gdb_window_size = get_gdb_height ();

    if (comptr->state == TAB_COMPLETION_START)
    {
        if_print ("\n");

        if (query_items > 0 && comptr->num_matches >= query_items)
        {
            if_print_message ("Display all %d possibilities? (y or n)\n",
                    comptr->num_matches);
            comptr->state = TAB_COMPLETION_QUERY_POSSIBILITIES;
            return 0;
        }

        comptr->state = TAB_COMPLETION_COMPLETION_DISPLAY;
    }

    if (comptr->state == TAB_COMPLETION_QUERY_POSSIBILITIES)
    {
        int val = Get_y_or_n (key, 0);
        if (val == 1)
            comptr->state = TAB_COMPLETION_COMPLETION_DISPLAY;
        else if (val == 0)
            comptr->state = TAB_COMPLETION_DONE;
        else
            return 0;		/* stay at the same state */
    }

    if (comptr->state == TAB_COMPLETION_QUERY_PAGER)
    {
        int i = Get_y_or_n (key, 1);
        if_clear_line (); /* Clear the --More-- */
        if (i == 0)
            comptr->state = TAB_COMPLETION_DONE;
        else if (i == 2)
        {
            comptr->lines--;
            comptr->state = TAB_COMPLETION_COMPLETION_DISPLAY;
        }
        else
        {
            comptr->lines = 0;
            comptr->state = TAB_COMPLETION_COMPLETION_DISPLAY;
        }
    }

    if (comptr->state == TAB_COMPLETION_COMPLETION_DISPLAY)
    {
        for (; comptr->total <= comptr->num_matches; comptr->total++)
        {
            if_print (comptr->matches[comptr->total]);
            if_print ("\n");

            comptr->lines++;
            if (comptr->lines >= (gdb_window_size - 1) &&
                    comptr->total < comptr->num_matches)
            {
                if_print ("--More--");
                comptr->state = TAB_COMPLETION_QUERY_PAGER;
                comptr->total++;
                return 0;
            }
        }

        comptr->state = TAB_COMPLETION_DONE;
    }

    if (comptr->state == TAB_COMPLETION_DONE)
    {
        Tab_completion_destroy (m_completion_ptr);
        m_completion_ptr = NULL;
        m_is_tab_completing = 0;
        rline_rl_forced_update_display (m_rline);
    }

    return 0;
}

/**
 * Get a yes or no from the user.
 *
 * \param key
 * The key that the user pressed
 *
 * \param for_pager
 * Will be non-zero if this is for the pager, otherwise 0.
 *
 * \return
 * 0 for no, 1 for yes, 2 for single line yes, -1 for nothing.
 */
int Screen::Get_y_or_n (int key, int for_pager)
{
    if (key == 'y' || key == 'Y' || key == ' ')
        return 1;
    if (key == 'n' || key == 'N')
        return 0;
    if (for_pager && (key == '\r' || key == '\n' || key == CGDB_KEY_CTRL_M))
        return 2;
    if (for_pager && (key == 'q' || key == 'Q'))
        return 0;
    if (key == CGDB_KEY_CTRL_G)
        return 0;

    return -1;
}

/**
 * Create a tab completion context.
 *
 * \param matches
 * See tab_completion field documentation
 * 
 * \param num_matches
 * See tab_completion field documentation
 *
 * \param max_length
 * See tab_completion field documentation
 *
 * \return
 * The next context, or NULL on error.
 */
tab_completion_ctx* Screen::Tab_completion_create (char **matches, int num_matches, int max_length)
{
    int i;
    tab_completion_ptr comptr;

    comptr = (tab_completion_ptr) cgdb_malloc (sizeof (struct tab_completion_ctx));

    comptr->matches = (char**) cgdb_malloc (sizeof (char *) * (num_matches + 1));
    for (i = 0; i <= num_matches; ++i)
        comptr->matches[i] = cgdb_strdup (matches[i]);

    comptr->num_matches = num_matches;
    comptr->max_length = max_length;
    comptr->total = 1;
    comptr->lines = 0;
    comptr->state = TAB_COMPLETION_START;

    return comptr;
}

/**
 * Destroy the tab completion context.
 *
 * \param comptr
 * This object to destroy.
 */
void Screen::Tab_completion_destroy (tab_completion_ptr comptr)
{
    int i;

    if (!comptr)
        return;

    if (comptr->matches == 0)
        return;

    for (i = 0; i <= comptr->num_matches; ++i)
    {
        free (comptr->matches[i]);
        comptr->matches[i] = NULL;
    }

    free (comptr->matches);
    comptr->matches = NULL;

    free (comptr);
    comptr = NULL;
}

void Screen::Ridline_completion_display_func (char **matches, int num_matches, int max_length)
{
    /* Create the tab completion item, and attempt to display it to the user */
    m_completion_ptr = Tab_completion_create (matches, num_matches, max_length);
    if (!m_completion_ptr)
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "Tab_completion_create error\n");

    if (Handle_tab_completion_request (m_completion_ptr, -1) == -1)
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "Handle_tab_completion_request error\n");
}

int Screen::Do_tab_completion (struct tgdb_list *list)
{
    if (rline_rl_complete (m_rline, list, &readline_completion_display_func) == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "rline_rl_complete error\n");
        return -1;
    }

    /** 
     * If m_completion_ptr is non-null, then this means CGDB still has to display
     * the completion to the user. m_is_tab_completing can not be turned off until
     * the completions are displayed to the user. */
    if (!m_completion_ptr)
        m_is_tab_completing = 0;

    return 0;
}

int Screen::Tab_completion (int a, int b)
{
    char *cur_line;
    int ret;
    tgdb_request_ptr request_ptr;

    m_is_tab_completing = 1;

    ret = rline_get_current_line (m_rline, &cur_line);
    if (ret == -1)
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "rline_get_current_line error\n");

    request_ptr = m_tgdb->Request_complete(cur_line);
    if (!request_ptr)
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "tgdb_request_complete error\n");

    Handle_request(request_ptr);
    return 0;
}

void Screen::Rl_sigint_recved (void)
{
    rline_clear (m_rline);
    m_is_tab_completing = 0;
}

void Screen::Rl_resize (int rows, int cols)
{
    rline_resize_terminal_and_redisplay (m_rline, rows, cols);
}

void Screen::Change_prompt (const char *new_prompt)
{
    rline_set_prompt (m_rline, new_prompt);
}


int Screen::Run_shell_command(const char *command) {

    int rv;
    char buf[64];

    /* Cleanly scroll the screen up for a prompt */
    scrl (1);
    move (LINES - 1, 0);
    printf ("\n");

    /* Put the terminal in cooked mode and turn on echo */
    endwin();
    tty_set_attributes(STDIN_FILENO, &m_term_attributes);

    /* NULL or empty string means invoke user's shell */
    if (command == NULL || strlen(command) == 0) {

        /* Check for SHELL environment variable */
        char *shell = getenv("SHELL");
        if (shell == NULL) {
            /* Run /bin/sh instead */
            rv = system("/bin/sh");
        } else {
            rv = system(shell);
        }
    } else {
        /* Execute the command passed in via system() */
        rv = system(command);
    }

    /* Press any key to continue... */
    fprintf(stderr, "Hit ENTER to continue...");
    fgets(buf, 64, stdin);

    /* Turn off echo and put the terminal back into raw mode */
    tty_cbreak(STDIN_FILENO, &m_term_attributes);
    if_draw();

    return rv;
}



/* Init_home_dir: Attempts to create a config directory in the user's home
 * -------------  directory.
 *
 * Return:  0 on sucess or -1 on error
 */

int Screen::Init_home_dir (void)
{
    /* Get the home directory */
    char *home_dir = getenv ("HOME");
    const char *cgdb_dir = ".cgdb";

    /* Create the config directory */
    if (!fs_util_create_dir_in_base (home_dir, cgdb_dir))
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "fs_util_create_dir_in_base error");
        return -1;
    }

    fs_util_get_path (home_dir, cgdb_dir, m_cgdb_home_dir);

    return 0;
}

int Screen::Process_gdb_commands()
{
    struct tgdb_response *item;

    while ((item = m_tgdb->Get_response()) != NULL)
    {
        switch (item->header)
        {
            /* This updates all the breakpoints */
            case TGDB_UPDATE_BREAKPOINTS:
                {
                    struct sviewer *sview = if_get_sview ();
                    char *file;
                    struct tgdb_list *list =
                        item->choice.update_breakpoints.breakpoint_list;
                    tgdb_list_iterator *iterator;
                    struct tgdb_breakpoint *tb;

                    source_clear_breaks (if_get_sview ());
                    iterator = tgdb_list_get_first (list);

                    while (iterator)
                    {
                        /* For each breakpoint */
                        tb = (struct tgdb_breakpoint *) tgdb_list_get_item (iterator);

                        file = tb->file;

                        if (tb->enabled)
                            source_enable_break (sview, file, tb->line);
                        else
                            source_disable_break (sview, file, tb->line);

                        iterator = tgdb_list_next (iterator);
                    }

                    if_show_file (NULL, 0);
                    break;
                }

                /* This means a source file or line number changed */
            case TGDB_UPDATE_FILE_POSITION:
                {
                    struct tgdb_file_position *tfp;
                    tfp = item->choice.update_file_position.file_position;

                    /* Update the file */
                    source_reload (if_get_sview (), tfp->absolute_path, 0);

                    if_show_file (tfp->absolute_path, tfp->line_number);

                    source_set_relative_path (if_get_sview (),
                            tfp->absolute_path, tfp->relative_path);

                    break;
                }

                /* This is a list of all the source files */
            case TGDB_UPDATE_SOURCE_FILES:
                { 
                    struct tgdb_list *list =
                        item->choice.update_source_files.source_files;
                    tgdb_list_iterator *i = tgdb_list_get_first (list);
                    char *s;

                    if_clear_filedlg ();

                    while (i)
                    {
                        s = (char*) tgdb_list_get_item (i);
                        if_add_filedlg_choice (s);
                        i = tgdb_list_next (i);
                    }

                    if_set_focus (FILE_DLG);
                    m_kui_input_acceptable = 1;
                    break;
                }

                /* The user is trying to get a list of source files that make up
                 * the debugged program but libtgdb is claiming that gdb knows
                 * none. */
            case TGDB_SOURCES_DENIED:
                if_display_message ("Error:", 0,
                        " No sources available! Was the program compiled with debug?");
                m_kui_input_acceptable = 1;
                break;

                /* This is the absolute path to the last file the user requested */
            case TGDB_FILENAME_PAIR:
                {
                    const char *apath = item->choice.filename_pair.absolute_path;
                    const char *rpath = item->choice.filename_pair.relative_path;
                    if_show_file ((char *) apath, 1);
                    source_set_relative_path (if_get_sview (), apath, rpath);
                    break;
                }

                /* The source file requested does not exist */
            case TGDB_ABSOLUTE_SOURCE_DENIED:
                {
                    struct tgdb_source_file *file =
                        item->choice.absolute_source_denied.source_file;
                    if_show_file (NULL, 0);
                    /* com can be NULL when tgdb orig requests main file */
                    if (file->absolute_path != NULL)
                        if_display_message ("No such file:", 0, " %s",
                                file->absolute_path);
                    break;
                }
            case TGDB_INFERIOR_EXITED:
                {
                    /*
                     * int *status = item->data;
                     * This could eventually go here, but for now, the update breakpoint 
                     * display function makes the status bar go back to the name of the file.
                     *
                     * if_display_message ( "Program exited with value", 0, " %d", *status );
                     */

                    /* Clear the cache */
                    break;
                }
            case TGDB_UPDATE_COMPLETIONS:
                {
                    struct tgdb_list *list =
                        item->choice.update_completions.completion_list;
                    Do_tab_completion (list);
                    break;
                }
            case TGDB_UPDATE_CONSOLE_PROMPT_VALUE:
                {
                    const char *new_prompt =
                        item->choice.update_console_prompt_value.prompt_value;
                    Change_prompt (new_prompt);
                    break;
                }
            case TGDB_QUIT:
                Cleanup();
                return -1;
                break;
                /* Default */
            default:
                break;
        }
    }
    return 0;
}

/* Gdb_input: Recieves data from tgdb:
 *
 *  Returns:  -1 on error, 0 on success
 */
int Screen::Gdb_input ()
{
    char *buf = (char*) malloc (GDB_MAXBUF + 1);
    int size;
    int is_finished;
    ITarget* target = NULL;
    
    /* Read from GDB */
    size = m_tgdb->Process(buf, GDB_MAXBUF, &is_finished, &target);
    if (size == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "tgdb_recv_debugger_data error");
        free (buf);
        buf = NULL;
        return -1;
    }

    buf[size] = 0;

    if(Process_gdb_commands())
        return -1;

    /* Display GDB output 
     * The strlen check is here so that if_print does not get called
     * when displaying the filedlg. If it does get called, then the 
     * gdb window gets displayed when the filedlg is up
     */
    if (strlen (buf) > 0) 
    {
        if(target != NULL)
            target->SendRespoce(buf);
        else
            if_print(buf);
    }
    free (buf);
    buf = NULL;

    /* Check to see if GDB is ready to recieve another command. If it is, then
     * readline should redisplay what it currently contains. There are 2 special
     * case's here.
     *
     * If there are no commands in the queue to send to GDB then  readline 
     * should simply redisplay what it has currently in it's buffer.
     * rl_forced_update_display does this.
     *
     * However, if there are commands in the queue to send to GDB, that means
     * the user already entered these through readline. In that case, simply 
     * write the command entered through readline directly, instead of having
     * readline do it (readline already processed the data). This is important
     * also because rl_forced_update_display doesn't write the data right away.
     * It writes data to rl_outstream, and then the main_loop handles both the
     * readline data and the data from the TGDB command being sent. This could
     * result in a race condition.
     */
    if (is_finished)
    {
        int size;
        m_tgdb->Queue_size(&size);
        /* This is the second case, this command was queued. */
        if (size > 0)
        {
            struct tgdb_request *request = m_tgdb->Queue_pop();
            char *prompt;
            if(target == NULL)
            {
                rline_get_prompt (m_rline, &prompt);
                if_print (prompt);
                m_last_request = request;
            }
            if (request->header == TGDB_REQUEST_CONSOLE_COMMAND)
            {
                if_print (request->choice.console_command.command);
                if_print ("\n");
            }

            m_tgdb->ProcessCommand(request);
            /* This is the first case */
        }
        /** If the user is currently completing, do not update the prompt */
        else
        {
            if(!m_completion_ptr)
            {
                int update = 0, ret_val;

                if (m_last_request)
                {
                    ret_val = Does_request_require_console_update(m_last_request, &update);
                    if (ret_val == -1)
                        return -1;
                    if(target == NULL)
                    {
                        if_disas_update (&m_last_request);
                        if_threads_send ();
                        if_call_stack_send ();
                    }
                    else
                        m_last_request = NULL;
                }
                if ((update)&&(target == NULL))
                    rline_rl_forced_update_display (m_rline);
            }  
        }
    }

    return 0;
}

/**
 * This function looks at the request that CGDB has made and determines if it 
 * effects the GDB console window. For instance, if the request makes output go
 * to that window, then the user would like to see another prompt there when the 
 * command finishes. However, if the command is 'o', to get all the sources and 
 * display them, then this doesn't effect the GDB console window and the window
 * should not redisplay the prompt.
 *
 * \param request
 * The request to analysize
 *
 * \param update
 * Will return as 1 if the console window should be updated, or 0 otherwise
 *
 * \return
 * 0 on success or -1 on error
 */
int Screen::Does_request_require_console_update(tgdb_request *request, int *update)
{
    if (!request || !update)
        return -1;

    switch (request->header)
    {
        case TGDB_REQUEST_CONSOLE_COMMAND:
            *update = 1;
            break;
        case TGDB_REQUEST_INFO_SOURCES:
        case TGDB_REQUEST_FILENAME_PAIR:
        case TGDB_REQUEST_CURRENT_LOCATION:
            *update = 0;
            break;
        case TGDB_REQUEST_DEBUGGER_COMMAND:
        case TGDB_REQUEST_MODIFY_BREAKPOINT:
        case TGDB_REQUEST_COMPLETE:
            if(request->choice.debugger_command.c == TGDB_REQUEST_DISAS)
                *update = 0;
            else
                *update = 1;
            break;
        default:
            return -1;
    }

    return 0;
}

/* ----------------- */
/* Exposed Functions */
/* ----------------- */

/* Cleanup: Invoked by the various err_xxx funtions when dying.
 * -------- */
void Screen::Cleanup()
{
    ibuf_free (m_current_line);

    /* Cleanly scroll the screen up for a prompt */
    scrl (1);
    move (LINES - 1, 0);
    printf ("\n");

    rline_write_history (m_rline, m_readline_history_path);

    /* The order of these is important. They each must restore the terminal
     * the way they found it. Thus, the order in which curses/readline is 
     * started, is the reverse order in which they should be shutdown 
     */

    /* Shut down interface */
    if_shutdown ();

#if 0
    if (masterfd != -1)
        util_free_tty (&masterfd, &slavefd, tty_name);
#endif

    if (tty_set_attributes (STDIN_FILENO, &m_term_attributes) == -1)
        m_tgdb->Logger_write_pos( __FILE__, __LINE__, "tty_reset error");

}

int Screen::Create_and_init_pair ()
{
    struct winsize size;
    int slavefd;

    m_pty_pair = pty_pair_create ();
    if (!m_pty_pair)
    {
        fprintf (stderr, "%s:%d Unable to create PTY pair", __FILE__, __LINE__);
        return -1;
    }

    slavefd = pty_pair_get_slavefd (m_pty_pair);
    if (slavefd == -1)
    {
        fprintf (stderr, "%s:%d Unable to create PTY pair", __FILE__, __LINE__);
        return -1;
    }

    /* Set the pty winsize to the winsize of stdout */
    if (ioctl (0, TIOCGWINSZ, &size) < 0)
        return -1;

    if (ioctl (slavefd, TIOCSWINSZ, &size) < 0)
        return -1;

    return 0;
}

int Screen::Add_readline_key_sequence (const char *readline_str, enum cgdb_key key)
{
    int ret_val;

    std_list keyseq_list = std_list_create (destroyReadlineKeySeq);

    ret_val = rline_get_keyseq (m_rline, readline_str, keyseq_list);
    if (ret_val == -1) {
        std_list_destroy (keyseq_list);
        return -1;
    }

    ret_val = kui_manager_get_terminal_keys_kui_map (m_kui_ctx, key, keyseq_list);
    if (ret_val == -1) {
        std_list_destroy (keyseq_list);
        return -1;
    }
    std_list_destroy (keyseq_list);

    return 0;
}

int Screen::Init_kui (void)
{
    m_kui_ctx = kui_manager_create (STDIN_FILENO, cgdbrc_get_key_code_timeoutlen (),
            cgdbrc_get_mapped_key_timeoutlen ());

    if (!m_kui_ctx)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "Unable to initialize input library");
        Cleanup();
        return -1;
    }

    m_kui_map = kui_ms_create ();
    if (!m_kui_map)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "Unable to initialize input library");
        Cleanup();
        return -1;
    }

    m_kui_imap = kui_ms_create ();
    if (!m_kui_imap)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "Unable to initialize input library");
        Cleanup();
        return -1;
    }

    if (kui_manager_add_map_set (m_kui_ctx, m_kui_map) == -1)
    {
        m_tgdb->Logger_write_pos( __FILE__, __LINE__,
                "Unable to initialize input library");
        Cleanup();
        return -1;
    }

    /* Combine the cgdbrc config package with libkui. If any of the options
     * below change, update the KUI.  Currently, the handles are not kept around,
     * because CGDB never plans on detaching. */
    cgdbrc_attach (CGDBRC_TIMEOUT, &update_kui, NULL);
    cgdbrc_attach (CGDBRC_TIMEOUT_LEN, &update_kui, NULL);
    cgdbrc_attach (CGDBRC_TTIMEOUT, &update_kui, NULL);
    cgdbrc_attach (CGDBRC_TTIMEOUT_LEN, &update_kui, NULL);

    /* It's important that CGDB uses readline's view of 
     * Home and End keys. A few distros I've run into (redhat e3
     * and ubuntu) provide incorrect terminfo entries for xterm.
     * So, Home and End do not work. The distro's fixed readline
     * by modifing /etc/inputrc to hard code the terminal sequences.
     * I have no idea why they wouldn't just fix the terminfo 
     * database, but they didn't! Therefor, readline, bash, gdb all
     * work but cgdb doesn't. So, I'm going to simply ask readline
     * what it thinks the Home and End keys are and add them to 
     * CGDB's mappings.
     */

    /* For now, I've decided it's OK for these functions to fail as they
     * only add functionality to CGDB. */

    /* Home key */
    Add_readline_key_sequence ("beginning-of-line", CGDB_KEY_HOME);
    /* End key */
    Add_readline_key_sequence ("end-of-line", CGDB_KEY_END);

    return 0;
}

